Limpieza de datos

Author

Equipo de Análisis

Published

Invalid Date

1 Limpieza de datos

1.0.1 Identificamos columnas innecesarias.

Uno de los primeros problemas que notamos es que la columna InicioVisitaPlanificado y FinVisitaPlanificado contienen los mismos valores.

# Podemos validarlo contando la cantidad de valores duplicados
sum(raw_data$InicioVisitaPlanificado == raw_data$FinVisitaPlanificado)
[1] 27484
# Comparamos la cantidad de duplicados (27484) con la cantidad de filas (27484).
dim(raw_data)
[1] 27484    16

Luego de verificar podemos unificar estas columnas en una nueva columna más intuitiva. “visita_planificada”

raw_data <- raw_data %>%
  # Nueva columna para almacenar el horario planificado
  mutate(visita_planificada = InicioVisitaPlanificado) %>%
  # Eliminamos InicioVisitaPlanificado y FinVisitaPlanificado
  select(-InicioVisitaPlanificado, -FinVisitaPlanificado)

1.0.2 Formato correcto de variables.

Nos aseguramos que todas las columnas tengan el formato correcto.

# Nombre de las columnas actuales
colnames(raw_data)
 [1] "iddomicilioorden"   "direccion"          "localidad"         
 [4] "InicioHorario1"     "FinHorario1"        "latitud"           
 [7] "longitud"           "cliente"            "mes"               
[10] "Bultos"             "Peso"               "Unidades"          
[13] "InicioVisitaReal"   "FinVisitaReal"      "visita_planificada"
# Clases de cada columna
sapply(raw_data, class)
$iddomicilioorden
[1] "numeric"

$direccion
[1] "character"

$localidad
[1] "character"

$InicioHorario1
[1] "numeric"

$FinHorario1
[1] "numeric"

$latitud
[1] "numeric"

$longitud
[1] "numeric"

$cliente
[1] "numeric"

$mes
[1] "numeric"

$Bultos
[1] "numeric"

$Peso
[1] "numeric"

$Unidades
[1] "numeric"

$InicioVisitaReal
[1] "character"

$FinVisitaReal
[1] "character"

$visita_planificada
[1] "POSIXct" "POSIXt" 

Tenemos que pasar a fecha las columnas: InicioVisitaReal, FinVisitaReal. Tambien nos aseguramos de que VisitaPlanificado tenga el mismo formato.

# Convertir cada columna a formato de fecha y hora
raw_data$InicioVisitaReal <- as.POSIXct(raw_data$InicioVisitaReal, format="%Y-%m-%d %H:%M:%OS")

raw_data$FinVisitaReal <- as.POSIXct(raw_data$FinVisitaReal, format="%Y-%m-%d %H:%M:%OS")

raw_data$visita_planificada <- as.POSIXct(raw_data$visita_planificada, format="%Y-%m-%d %H:%M:%OS")

Las columnas InicioHorario1, FinHorario1, las pasamos a caracter para categorizarlas más facil.

# Convertir las columnas a carácter
raw_data$InicioHorario1 <- as.character(raw_data$InicioHorario1)

raw_data$FinHorario1 <- as.character(raw_data$FinHorario1)

1.1 Pasamos variables categóricas a factores.

raw_data$cliente <- as.factor(raw_data$cliente)

1.2 Renombramos las columnas

Para facilitar el uso del dataset renombramos las columnas con nombres más simples y descriptivos.

colnames(raw_data)
 [1] "iddomicilioorden"   "direccion"          "localidad"         
 [4] "InicioHorario1"     "FinHorario1"        "latitud"           
 [7] "longitud"           "cliente"            "mes"               
[10] "Bultos"             "Peso"               "Unidades"          
[13] "InicioVisitaReal"   "FinVisitaReal"      "visita_planificada"
# Renombrar columnas específicas con dplyr
raw_data <- raw_data %>%
  rename(      id_orden = iddomicilioorden,
         inicio_horario = InicioHorario1,
            fin_horario = FinHorario1,
                 bultos = Bultos,
                   peso = Peso,
               unidades = Unidades,
          inicio_visita = InicioVisitaReal,
             fin_visita = FinVisitaReal)
colnames(raw_data)
 [1] "id_orden"           "direccion"          "localidad"         
 [4] "inicio_horario"     "fin_horario"        "latitud"           
 [7] "longitud"           "cliente"            "mes"               
[10] "bultos"             "peso"               "unidades"          
[13] "inicio_visita"      "fin_visita"         "visita_planificada"

1.3 Reorganizar columnas.

raw_data <- raw_data %>%
  select(id_orden, cliente, localidad, direccion, latitud, longitud,
         bultos, unidades, peso, inicio_horario, fin_horario, visita_planificada, inicio_visita, fin_visita)

1.4 Limpieza de datos nulos

Antes de crear nuevas variables vamos a asegurarnos de que los datos con los que vamos a trabajar sean correctos.

# Obtener un resumen completo del dataframe
skim(raw_data)
Data summary
Name raw_data
Number of rows 27484
Number of columns 14
_______________________
Column type frequency:
character 4
factor 1
numeric 6
POSIXct 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
localidad 0 1 5 31 0 44 0
direccion 0 1 7 49 0 6072 0
inicio_horario 0 1 1 3 0 3 0
fin_horario 0 1 4 4 0 3 0

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
cliente 0 1 FALSE 2 20: 16604, 70: 10880

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id_orden 0 1 101860.16 33957.94 74956.00 77468.00 82380.00 122555.00 183277 ▇▁▁▁▂
latitud 41 1 -34.59 0.30 -34.93 -34.62 -34.60 -34.58 0 ▇▁▁▁▁
longitud 41 1 -58.44 0.52 -68.74 -58.48 -58.44 -58.40 0 ▇▁▁▁▁
bultos 0 1 5.76 12.23 0.10 2.00 3.00 6.00 360 ▇▁▁▁▁
unidades 0 1 28.37 61.75 1.00 2.00 6.00 40.00 2203 ▇▁▁▁▁
peso 0 1 41.13 79.07 0.00 13.00 20.92 39.00 2475 ▇▁▁▁▁

Variable type: POSIXct

skim_variable n_missing complete_rate min max median n_unique
visita_planificada 0 1 2024-05-03 12:01:00 2024-08-06 12:01:00 2024-06-18 12:02:00 6797
inicio_visita 52 1 2024-05-03 10:17:51 2024-08-06 19:57:00 2024-06-18 13:29:31 16357
fin_visita 52 1 2024-05-03 11:08:53 2024-08-06 19:57:00 2024-06-18 13:42:33 16309
# Contar la cantidad de valores NA por columna
colSums(is.na(raw_data))
          id_orden            cliente          localidad          direccion 
                 0                  0                  0                  0 
           latitud           longitud             bultos           unidades 
                41                 41                  0                  0 
              peso     inicio_horario        fin_horario visita_planificada 
                 0                  0                  0                  0 
     inicio_visita         fin_visita 
                52                 52 
# Verificar si hay filas duplicadas
sum(duplicated(raw_data))
[1] 46
# Mostrar las filas duplicadas
raw_data[duplicated(raw_data), ] %>% arrange(desc(id_orden))
# A tibble: 46 × 14
   id_orden cliente localidad   direccion latitud longitud bultos unidades  peso
      <dbl> <fct>   <chr>       <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
 1   182218 20      HUDSON      CALLE 63…   -34.8    -58.1     10       10   100
 2   179581 20      CAPITAL FE… AEROPARQ…   -34.6    -58.4     10       10    10
 3   179581 20      CAPITAL FE… AEROPARQ…   -34.6    -58.4     10       10    10
 4   179581 20      CAPITAL FE… AEROPARQ…   -34.6    -58.4     10       10    10
 5   179276 20      CAPITAL FE… CARLOS P…   -34.6    -58.4      4        4    26
 6   179142 20      CAPITAL FE… AV CORRI…   -34.6    -58.4      6        6     6
 7   177940 20      CAPITAL FE… Florida …   -34.6    -58.4     12       12    12
 8   177939 20      CAPITAL FE… AV.PUEYR…   -34.6    -58.4     12       12    12
 9   177938 20      CAPITAL FE… FLORIDA …   -32.9    -68.7     12       12    12
10   177937 20      CAPITAL FE… Florida …   -32.9    -68.7     12       12    12
# ℹ 36 more rows
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>
# Verificamos una observación duplicada
raw_data %>% filter(fin_visita == "2024-07-05 08:58:36")
# A tibble: 2 × 14
  id_orden cliente localidad    direccion latitud longitud bultos unidades  peso
     <dbl> <fct>   <chr>        <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
1    82384 20      CAPITAL FED… LAFINUR …   -34.6    -58.4      9        9    45
2    82384 20      CAPITAL FED… LAFINUR …   -34.6    -58.4      9        9    45
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>

Mientras explorabamos las filas duplicadas encontramos que existe una gran cantidad de datos con el mismo horario y feecha de fin de visita. Y en estos casos coincide tambien con el horario de inicio.

raw_data %>% filter(fin_visita == "2024-07-03 16:55:00")
# A tibble: 18 × 14
   id_orden cliente localidad  direccion latitud longitud bultos unidades   peso
      <dbl> <fct>   <chr>      <chr>       <dbl>    <dbl>  <dbl>    <dbl>  <dbl>
 1    76228 70      CAPITAL    Sanchez …   -34.6    -58.4   4.5         9  31.5 
 2    76928 70      CAPITAL    Santiago…   -34.6    -58.4   3.1        36  28.9 
 3    76997 70      SIN LOCAL… RINCON 7…   -34.6    -58.4   3.6        51  23.4 
 4    77067 70      CAPITAL    ADOLFO A…   -34.6    -58.4   3.23       52  25.4 
 5    77123 70      CAPITAL    HUMBERTO…   -34.6    -58.4   5.55       81  35.4 
 6    77173 70      CAPITAL    CATAMARC…   -34.6    -58.4   6.92       94  48.2 
 7    79473 70      CAPITAL    VENEZUEL…   -34.6    -58.4  20         240 240   
 8    79482 70      Villa Ort… HIPOLITO…   -34.6    -58.4   5.82       84  36.5 
 9    79490 70      CAPITAL    HIPOLITO…   -34.6    -58.4   2.65       53   8.40
10    79552 70      CAPITAL    AV.ENTRE…   -34.6    -58.4   5.7        77  42.4 
11    79604 70      CAPITAL    AVDA RIV…   -34.6    -58.4   6.32      101  30.0 
12    99060 70      CAPITAL    JUJUY 457   -34.6    -58.4   3.35       65  10.3 
13   100429 70      CAPITAL    RIVADAVI…   -34.6    -58.4   7.37      105  41.0 
14   123181 70      CAPITAL    PENA 2475   -34.6    -58.4  11         135 107.  
15   123181 70      CAPITAL    PENA 2475   -34.6    -58.4  11         135 107.  
16   175069 70      CAPITAL    SOLIS 757   -34.6    -58.4   3.33       56  15.1 
17   177690 70      CAPITAL    SANTIAGO…   -34.6    -58.4   4.2        56  31.3 
18   177922 70      CAPITAL    MISIONES…   -34.6    -58.4   7.12      103  44.8 
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>
summary(raw_data)
    id_orden      cliente     localidad          direccion        
 Min.   : 74956   20:16604   Length:27484       Length:27484      
 1st Qu.: 77468   70:10880   Class :character   Class :character  
 Median : 82380              Mode  :character   Mode  :character  
 Mean   :101860                                                   
 3rd Qu.:122555                                                   
 Max.   :183277                                                   
                                                                  
    latitud          longitud          bultos           unidades      
 Min.   :-34.93   Min.   :-68.74   Min.   :  0.100   Min.   :   1.00  
 1st Qu.:-34.62   1st Qu.:-58.48   1st Qu.:  2.000   1st Qu.:   2.00  
 Median :-34.60   Median :-58.44   Median :  3.000   Median :   6.00  
 Mean   :-34.59   Mean   :-58.44   Mean   :  5.761   Mean   :  28.37  
 3rd Qu.:-34.58   3rd Qu.:-58.40   3rd Qu.:  6.000   3rd Qu.:  40.00  
 Max.   :  0.00   Max.   :  0.00   Max.   :360.000   Max.   :2203.00  
 NA's   :41       NA's   :41                                          
      peso         inicio_horario     fin_horario       
 Min.   :   0.00   Length:27484       Length:27484      
 1st Qu.:  13.00   Class :character   Class :character  
 Median :  20.92   Mode  :character   Mode  :character  
 Mean   :  41.13                                        
 3rd Qu.:  39.00                                        
 Max.   :2475.00                                        
                                                        
 visita_planificada               inicio_visita                   
 Min.   :2024-05-03 12:01:00.00   Min.   :2024-05-03 07:17:51.00  
 1st Qu.:2024-05-24 12:59:00.00   1st Qu.:2024-05-24 15:38:00.00  
 Median :2024-06-18 12:02:00.00   Median :2024-06-18 10:29:31.00  
 Mean   :2024-06-17 20:55:17.65   Mean   :2024-06-17 22:30:40.35  
 3rd Qu.:2024-07-11 12:07:00.00   3rd Qu.:2024-07-11 11:42:07.00  
 Max.   :2024-08-06 12:01:00.00   Max.   :2024-08-06 16:57:00.00  
                                  NA's   :52                      
   fin_visita                    
 Min.   :2024-05-03 08:08:53.00  
 1st Qu.:2024-05-24 15:38:00.00  
 Median :2024-06-18 10:42:33.00  
 Mean   :2024-06-17 22:36:39.14  
 3rd Qu.:2024-07-11 11:49:58.50  
 Max.   :2024-08-06 16:57:00.00  
 NA's   :52                      

1.4.1 Problemas encontrados.

El icono “🟩“ indica solucionado.

Encontramos

  • 41 valores nulos en latitud y longitud 🟩

  • 52 valores nulos en inicio_visita y fin_visita

  • 46 observaciones duplicadas. 🟩

  • Valores de “0” en latitud y longitud. 🟩

  • Peso mínimo igual a 0

1.4.1.1 Eliminamos filas duplicadas

raw_data <- raw_data %>%
  distinct()

dim(raw_data)
[1] 27438    14

1.4.1.2 Valores nulos o “0” en coordenadas

Para los valores nulos encontrados en cordenadas validamos si ya se encuentra algun domicilio con los datos cargados.

# Filtrar las filas donde latitud o longitud son NA
cordenadas_vacias <- raw_data %>%
  filter(
    is.na(latitud) | is.na(longitud) | latitud == 0 | longitud == 0
    )

cordenadas_vacias # dim 43 x 14
# A tibble: 42 × 14
   id_orden cliente localidad   direccion latitud longitud bultos unidades  peso
      <dbl> <fct>   <chr>       <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
 1   163957 20      CAPITAL FE… DR ENRIQ…       0        0   2           2  13  
 2   167030 70      CAPITAL     JOSE HER…      NA       NA   2.44       55  10.8
 3   173085 20      GENERAL PA… CTRO COM…       0        0   4           4   0  
 4   176714 20      CAPITAL FE… AV. R. S…      NA       NA   5           5  39  
 5   176717 20      CAPITAL FE… AV. CORR…      NA       NA   2           2  19.5
 6   176731 70      CAPITAL     VEDIA 36…      NA       NA   5.5        18  50.2
 7   177884 20      CAPITAL FE… LARREA 1…      NA       NA   3           3  19.5
 8   177934 20      CAPITAL FE… AV CORRI…      NA       NA  12          12  12  
 9   177988 20      CAPITAL FE… CUBA 291…      NA       NA   5           5   0  
10   178012 70      CAPITAL     AREVALO …      NA       NA   2           2  10  
# ℹ 32 more rows
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>
distinct(cordenadas_vacias)
# A tibble: 42 × 14
   id_orden cliente localidad   direccion latitud longitud bultos unidades  peso
      <dbl> <fct>   <chr>       <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
 1   163957 20      CAPITAL FE… DR ENRIQ…       0        0   2           2  13  
 2   167030 70      CAPITAL     JOSE HER…      NA       NA   2.44       55  10.8
 3   173085 20      GENERAL PA… CTRO COM…       0        0   4           4   0  
 4   176714 20      CAPITAL FE… AV. R. S…      NA       NA   5           5  39  
 5   176717 20      CAPITAL FE… AV. CORR…      NA       NA   2           2  19.5
 6   176731 70      CAPITAL     VEDIA 36…      NA       NA   5.5        18  50.2
 7   177884 20      CAPITAL FE… LARREA 1…      NA       NA   3           3  19.5
 8   177934 20      CAPITAL FE… AV CORRI…      NA       NA  12          12  12  
 9   177988 20      CAPITAL FE… CUBA 291…      NA       NA   5           5   0  
10   178012 70      CAPITAL     AREVALO …      NA       NA   2           2  10  
# ℹ 32 more rows
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>

Revisamos para cada entrega con valores faltantes si existe alguna orden con los datos cargados correctamente.

# Filtrar las observaciones donde id_orden está en cordenadas_vacias
observaciones_id_orden <- raw_data %>%
  filter(id_orden %in% cordenadas_vacias$id_orden) %>%
  group_by(id_orden) %>%
  summarise(count = n())

# Mostrar el resultado
observaciones_id_orden
# A tibble: 39 × 2
   id_orden count
      <dbl> <int>
 1   163957     2
 2   167030     1
 3   173085     9
 4   176714     2
 5   176717     1
 6   176731     1
 7   177884     3
 8   177934     2
 9   177988     2
10   178012    10
# ℹ 29 more rows
# Contar las apariciones de cada id_orden en cordenadas_vacias
apariciones_cordenadas_vacias <- cordenadas_vacias %>%
  group_by(id_orden) %>%
  summarise(na_count = n())

# Unir las tablas por id_orden
resultado <- observaciones_id_orden %>%
  left_join(apariciones_cordenadas_vacias, by = "id_orden") %>%
  # Si no hay coincidencias en cordenadas_vacias, establecer na_count en 0
  mutate(na_count = ifelse(is.na(na_count), 0, na_count)) %>%
  # Restar las apariciones de cordenadas_vacias del total
  mutate(count_diff = count - na_count) %>%

# Filtrar solo los id_orden donde count_diff es mayor a 0
  filter(count_diff > 0)

# Mostrar el resultado
resultado
# A tibble: 21 × 4
   id_orden count na_count count_diff
      <dbl> <int>    <int>      <int>
 1   163957     2        1          1
 2   173085     9        1          8
 3   176714     2        1          1
 4   177884     3        1          2
 5   177934     2        1          1
 6   177988     2        1          1
 7   178012    10        1          9
 8   179363     2        1          1
 9   179364     2        1          1
10   180498     2        1          1
# ℹ 11 more rows

Podemos reemplazar la latitud y longitud en 21 de las 42 filas. Podemos recuperar un 50% de los datos con latitud y longitud faltante. Para esto creamos una función

# Verificamos una observación
raw_data %>% filter(id_orden == 177934)
# A tibble: 2 × 14
  id_orden cliente localidad    direccion latitud longitud bultos unidades  peso
     <dbl> <fct>   <chr>        <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
1   177934 20      CAPITAL FED… AV CORRI…    NA       NA       12       12    12
2   177934 20      CAPITAL FED… AV CORRI…   -34.6    -58.4      3        3     4
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>
# Cantidad previa de NA (40)
summary(raw_data$longitud) 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
 -68.74  -58.48  -58.44  -58.44  -58.40    0.00      40 
# Definir la función que revisa y sobrescribe latitud y longitud
reparar_lat_long <- function(dataset, ids) {
  # Iterar sobre cada id de la lista
  for (id in ids) {
    # Filtrar las observaciones válidas de latitud y longitud para este id_orden
    observaciones_validas <- dataset %>%
      filter(id_orden == id & !is.na(latitud) & !is.na(longitud) & latitud != 0 & longitud != 0)
    
    # Si existen observaciones válidas, tomar la primera ocurrencia
    if (nrow(observaciones_validas) > 0) {
      latitud_valida <- observaciones_validas$latitud[1]
      longitud_valida <- observaciones_validas$longitud[1]
      
      # Sobrescribir las observaciones con latitud o longitud nulos o 0
      dataset <- dataset %>%
        mutate(
          latitud = ifelse(id_orden == id & (is.na(latitud) | latitud == 0), latitud_valida, latitud),
          longitud = ifelse(id_orden == id & (is.na(longitud) | longitud == 0), longitud_valida, longitud)
        )
    }
  }
  
  # Retornar el dataset reparado
  return(dataset)
}

# Ejecutar la función usando los id_orden de la columna resultado
ids_a_reparar <- resultado$id_orden

# Aplicar la función a raw_data
raw_data <- reparar_lat_long(raw_data, ids_a_reparar)

# Verificar los cambios
head(raw_data)
# A tibble: 6 × 14
  id_orden cliente localidad direccion  latitud longitud bultos unidades  peso
     <dbl> <fct>   <chr>     <chr>        <dbl>    <dbl>  <dbl>    <dbl> <dbl>
1    74956 70      CAPITAL   VIDAL 2044   -34.6    -58.5   2.52       30  24.9
2    74956 70      CAPITAL   VIDAL 2044   -34.6    -58.5   2.87       38  26.3
3    74956 70      CAPITAL   VIDAL 2044   -34.6    -58.5   2.4        32  24.9
4    74956 70      CAPITAL   VIDAL 2044   -34.6    -58.5   1.8        28  14.0
5    74956 70      CAPITAL   VIDAL 2044   -34.6    -58.5   2.12       31  14.4
6    74956 70      CAPITAL   VIDAL 2044   -34.6    -58.5   1.82       25  13.8
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>
# Comprobamos que funciona.
raw_data %>% filter(id_orden == 177934)
# A tibble: 2 × 14
  id_orden cliente localidad    direccion latitud longitud bultos unidades  peso
     <dbl> <fct>   <chr>        <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
1   177934 20      CAPITAL FED… AV CORRI…   -34.6    -58.4     12       12    12
2   177934 20      CAPITAL FED… AV CORRI…   -34.6    -58.4      3        3     4
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>

Con todos estos cambios comprobamos la cantidad de datos vacios en latitud / longitud.

# Nueva cantidad de NA (19), 21 solucionados.
summary(raw_data$longitud) 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
 -68.74  -58.48  -58.44  -58.44  -58.40  -57.94      19 

Quedaron 19 sin resolver, eliminamos estas filas.

# Eliminar las filas donde latitud o longitud es NA
raw_data <- raw_data %>%
  filter(!is.na(latitud) & !is.na(longitud))

dim(raw_data)
[1] 27419    14

1.4.1.3 Valores nulos en inicio visita y fin visita

raw_data %>% filter(is.na(inicio_visita) | is.na(fin_visita) )
# A tibble: 52 × 14
   id_orden cliente localidad   direccion latitud longitud bultos unidades  peso
      <dbl> <fct>   <chr>       <chr>       <dbl>    <dbl>  <dbl>    <dbl> <dbl>
 1    75370 20      CAPITAL FE… AVDA RIV…   -34.6    -58.4      3        3  15  
 2    79712 20      CAPITAL FE… CORDOBA …   -34.6    -58.4      2        2  11.9
 3    79827 20      CAPITAL FE… MITRE 22…   -34.6    -58.4      2        2  13  
 4    83443 20      CAPITAL FE… B MITRE …   -34.6    -58.4      2        2  13  
 5    86129 20      CAPITAL FE… URIBURU …   -34.6    -58.4      2        2  13  
 6    86163 20      CAPITAL FE… AV. CORR…   -34.6    -58.4      2        2  13  
 7    91689 20      CAPITAL FE… PASTEUR …   -34.6    -58.4      2        2  10.8
 8    97879 20      CAPITAL FE… AV. CORR…   -34.6    -58.4      2        2  13  
 9    99075 20      CAPITAL FE… PARAGUAY…   -34.6    -58.4      2        2  11.9
10    99076 20      CAPITAL FE… AV. CORD…   -34.6    -58.4      2        2  10.8
# ℹ 42 more rows
# ℹ 5 more variables: inicio_horario <chr>, fin_horario <chr>,
#   visita_planificada <dttm>, inicio_visita <dttm>, fin_visita <dttm>
# Cantidad de valores con horarios iguales
dim(raw_data %>% filter(inicio_visita == fin_visita))
[1] 10225    14

Como un gran porcentaje de los datos tiene el mismo inicio_visita y fin_visita almacenamos en variables distintas para intentar identificar un motivo.

# Guardamos en una variable los valores con horarios duplicados
datos_visita_duplicados <- raw_data %>%
  filter(inicio_visita == fin_visita)

# Tambien una variable sin los duplicados
datos_visita_sin_duplicado <- raw_data %>%
  filter(inicio_visita != fin_visita)
# Ver los valores únicos y la cantidad de cada uno
raw_data %>%
  count(fin_horario)
# A tibble: 3 × 2
  fin_horario     n
  <chr>       <int>
1 1400            6
2 1401        27412
3 2359            1

1.4.2 Creación de nuevas variables.

Creamos nuevas columnas útiles para futuros analisis.

colnames(raw_data)
 [1] "id_orden"           "cliente"            "localidad"         
 [4] "direccion"          "latitud"            "longitud"          
 [7] "bultos"             "unidades"           "peso"              
[10] "inicio_horario"     "fin_horario"        "visita_planificada"
[13] "inicio_visita"      "fin_visita"        
raw_data <- raw_data %>%
  mutate(
    dia = as.integer(format(fin_visita, "%d")),   # Extraer el día
    mes = as.integer(format(fin_visita, "%m")),   # Extraer el mes
    hora = as.integer(format(fin_visita, "%H")), 
    
    diferencia_minutos = as.numeric(
      difftime(fin_visita, visita_planificada, units = "mins")),
    dia_str = weekdays(fin_visita, abbreviate = FALSE),
    
    duracion_visita_min = as.numeric(
      difftime(fin_visita, inicio_visita, units = "mins")),
    
         duracion_visita_horas = as.numeric(
           difftime(fin_visita, inicio_visita, units = "hours"))
    
  )

1.4.3 Resultados de la limpieza

# Tabla de valores faltantes por columna
inspect_na(raw_data) %>%
  show_plot()

# Visualizar los valores faltantes
plot_missing(raw_data)

skim(raw_data)
Data summary
Name raw_data
Number of rows 27419
Number of columns 21
_______________________
Column type frequency:
character 5
factor 1
numeric 12
POSIXct 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
localidad 0 1 5 31 0 43 0
direccion 0 1 7 49 0 6060 0
inicio_horario 0 1 1 3 0 3 0
fin_horario 0 1 4 4 0 3 0
dia_str 52 1 5 9 0 7 0

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
cliente 0 1 FALSE 2 20: 16545, 70: 10874

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id_orden 0 1 101739.25 33854.63 74956.00 77464.50 82362.00 121907.00 183277.00 ▇▁▁▁▂
latitud 0 1 -34.60 0.04 -34.93 -34.62 -34.60 -34.58 -32.94 ▇▁▁▁▁
longitud 0 1 -58.44 0.12 -68.74 -58.48 -58.44 -58.40 -57.94 ▁▁▁▁▇
bultos 0 1 5.76 12.25 0.10 2.00 3.00 6.00 360.00 ▇▁▁▁▁
unidades 0 1 28.40 61.81 1.00 2.00 6.00 40.00 2203.00 ▇▁▁▁▁
peso 0 1 41.16 79.15 0.00 13.00 20.94 39.00 2475.00 ▇▁▁▁▁
dia 52 1 15.83 8.82 1.00 8.00 15.00 24.00 31.00 ▇▇▇▆▆
mes 52 1 6.06 0.85 5.00 5.00 6.00 7.00 8.00 ▇▇▁▇▁
hora 52 1 14.12 2.02 7.00 13.00 14.00 16.00 23.00 ▁▅▇▂▁
diferencia_minutos 52 1 319.79 238.73 -6820.00 217.91 321.98 403.00 7341.00 ▁▁▇▁▁
duracion_visita_min 52 1 5.98 10.55 0.00 0.00 2.98 8.67 391.07 ▇▁▁▁▁
duracion_visita_horas 52 1 0.10 0.18 0.00 0.00 0.05 0.14 6.52 ▇▁▁▁▁

Variable type: POSIXct

skim_variable n_missing complete_rate min max median n_unique
visita_planificada 0 1 2024-05-03 12:01:00 2024-08-06 12:01:00 2024-06-18 12:03:00 6795
inicio_visita 52 1 2024-05-03 10:17:51 2024-08-06 19:57:00 2024-06-18 14:01:44 16350
fin_visita 52 1 2024-05-03 11:08:53 2024-08-06 19:57:00 2024-06-18 14:19:11 16302

2 Análisis Exploratorio de Datos

Descripcion aca

2.1 Resumen general

plot_intro(raw_data)

raw_data %>%
  inspect_cat() %>%    # library(inspectdf)
  show_plot()

2.2 Distribución de variables numéricas

Para entender la distribución de las variables numéricas como diferencia_minutos, duracion_visita_min, duracion_visita_horas, puedes visualizar los histogramas:

# Visualización de la distribución de variables numéricas
plot_histogram(raw_data)

2.3 Análisis de correlación entre variables numéricas

# Seleccionar solo las variables numéricas
numericas <- raw_data %>%
  select_if(is.numeric)

# Calcular la matriz de correlación
correlacion <- cor(numericas, use = "complete.obs")

# Visualizar la matriz de correlación con plotly
plot_ly(z = correlacion, 
        x = colnames(correlacion), 
        y = rownames(correlacion), 
        type = "heatmap", 
        colorscale = "Viridis") %>%
  layout(title = "Matriz de Correlación entre Variables Numéricas",
         xaxis = list(title = ""),
         yaxis = list(title = ""))

2.4 Distribución de variables categóricas

2.4.1 Intentamos identificar algun patron en las entregas segun el día de la semana

Warning: Ignoring 1 observations
# Visualización de la distribución de visitas por cliente
distribucion_cliente <- raw_data %>%
  count(cliente)

plot_ly(distribucion_cliente, 
        x = ~cliente, 
        y = ~n, 
        type = 'bar', 
        marker = list(color = 'rgba(26, 118, 255, 0.6)', 
                      line = list(color = 'rgba(26, 118, 255, 1.0)', width = 2))) %>%
  layout(title = "Distribución de Visitas por Cliente",
         xaxis = list(title = "Cliente"),
         yaxis = list(title = "Cantidad de Visitas"),
         showlegend = FALSE)

2.5 Relación entre variables numéricas y categóricas

# Relación entre 'dia_str' (día de la semana) y 'duracion_visita_min'
ggplot(raw_data, aes(x = dia_str, y = duracion_visita_min)) +
  geom_boxplot() +
  theme_minimal() +
  labs(title = "Duración de la visita por día de la semana", x = "Día", y = "Duración de la visita (minutos)")
Warning: Removed 52 rows containing non-finite outside the scale range
(`stat_boxplot()`).

2.6 Visualización de variables temporales

# Contar visitas por día de la semana
raw_data %>%
  count(dia_str) %>%
  ggplot(aes(x = dia_str, y = n)) +
  geom_bar(stat = "identity") +
  labs(title = "Cantidad de visitas por día de la semana", x = "Día", y = "Número de visitas") +
  theme_minimal()

2.7 Otros gráficos

raw_data$visita_planificada <- as.Date(raw_data$visita_planificada)

# Contar la cantidad de entregas por cliente y fecha
entregas_por_cliente <- raw_data %>%
  group_by(visita_planificada, cliente) %>%
  summarise(cantidad_entregas = n()) %>%
  ungroup()
`summarise()` has grouped output by 'visita_planificada'. You can override
using the `.groups` argument.
# Graficar con plotly
plot_ly(entregas_por_cliente, 
        x = ~visita_planificada, 
        y = ~cantidad_entregas, 
        color = ~cliente, 
        type = 'scatter', 
        mode = 'lines+markers') %>%
  layout(title = "Cantidad de Entregas por Fecha para Cada Cliente",
         xaxis = list(title = "Fecha de Visita Planificada"),
         yaxis = list(title = "Cantidad de Entregas"),
         legend = list(title = list(text = "Cliente")))
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Notamos que la caida del 7 de julio representa una entrega un día domingo. Día donde tipicamente no se realizan entregas. Tambien es una entrega con mucha demora.

# Asegúrate de que la columna que contiene la fecha esté en formato de fecha
raw_data$visita_planificada <- as.Date(raw_data$visita_planificada)

# Calcular el tiempo de demora en horas
raw_data <- raw_data %>%
  mutate(demora_horas = as.numeric(difftime(fin_visita, visita_planificada, units = "min")))

# Calcular el tiempo de demora promedio por cliente y fecha
demora_por_cliente <- raw_data %>%
  group_by(visita_planificada, cliente) %>%
  summarise(demora_promedio = mean(demora_horas, na.rm = TRUE)) %>%
  ungroup()
`summarise()` has grouped output by 'visita_planificada'. You can override
using the `.groups` argument.
# Graficar con plotly
plot_ly(demora_por_cliente, 
        x = ~visita_planificada, 
        y = ~demora_promedio, 
        color = ~cliente, 
        type = 'scatter', 
        mode = 'lines+markers') %>%
  layout(title = "Tiempo de Demora Promedio (en horas) por Fecha y Cliente",
         xaxis = list(title = "Fecha de Visita Planificada"),
         yaxis = list(title = "Demora Promedio (horas)"),
         legend = list(title = list(text = "Cliente")))
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
# Graficar las entregas por cliente con colores distintos
plot_ly(
  raw_data,
  lat = ~latitud,
  lon = ~longitud,
  type = 'scattermapbox',
  mode = 'markers',
  color = ~cliente,  # Asigna un color distinto por cliente
  marker = list(size = 7, opacity = 0.3),  # Ajusta el tamaño y la transparencia de los marcadores
  text = ~paste("Cliente:", cliente, "<br>Dirección:", direccion)  # Información al pasar el mouse
) %>%
  layout(
    mapbox = list(
      accesstoken = mapbox_token,
      center = list(lat = -34.6037, lon = -58.3816),  # Coordenadas de Buenos Aires
      zoom = 10,  # Nivel de zoom
      style = "open-street-map"  # Estilo del mapa
    ),
    title = "Mapa de Entregas en Buenos Aires por Cliente",
    margin = list(r = 0, t = 0, b = 0, l = 0)
  )
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
# Calcular el tiempo de demora en horas
copy_data <- raw_data %>%
  mutate(demora_horas = as.numeric(difftime(fin_visita, visita_planificada, units = "hours")))

# Calcular la cantidad de entregas por día por cliente
entregas_por_cliente <- copy_data %>%
  group_by(visita_planificada, cliente) %>%
  summarise(cantidad_entregas = n(),
            demora_promedio = mean(demora_horas, na.rm = TRUE)) %>%
  ungroup()
`summarise()` has grouped output by 'visita_planificada'. You can override
using the `.groups` argument.
# Graficar usando plotly con dos ejes Y
plot_ly() %>%
  # Línea de cantidad de entregas por día por cliente
  add_lines(data = entregas_por_cliente, 
            x = ~visita_planificada, 
            y = ~cantidad_entregas, 
            color = ~cliente, 
            name = "Cantidad de Entregas",
            yaxis = "y1") %>%
  
  # Línea de tiempo promedio de demora por día por cliente
  add_lines(data = entregas_por_cliente, 
            x = ~visita_planificada, 
            y = ~demora_promedio, 
            color = ~cliente, 
            name = "Demora Promedio (Horas)",
            line = list(dash = 'dash'),  # Línea punteada para diferenciarlas
            yaxis = "y2") %>%
  
  # Configuración de los ejes
  layout(
    title = "Entregas por Día y Tiempo Promedio de Demora por Cliente",
    xaxis = list(title = "Fecha"),
    yaxis = list(title = "Cantidad de Entregas", side = "left", showgrid = FALSE),
    yaxis2 = list(title = "Demora Promedio (Horas)", side = "right", overlaying = "y", showgrid = FALSE),
    legend = list(x = 0.1, y = 0.9)
  )
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
# Entregas por semana
# Convertir la columna de fecha a formato Date (si no lo está)
raw_data$fin_visita <- as.Date(raw_data$visita_planificada)

# Crear una columna que agrupe por semana (añadiendo el lunes como inicio de semana)
raw_data$semana_visita <- cut(raw_data$visita_planificada, breaks = "week", start.on.monday = TRUE)

# Contar la cantidad de entregas por cliente y semana
entregas_por_cliente <- raw_data %>%
  group_by(semana_visita, cliente) %>%
  summarise(cantidad_entregas = n()) %>%
  ungroup()
`summarise()` has grouped output by 'semana_visita'. You can override using the
`.groups` argument.
# Graficar con plotly
plot_ly(entregas_por_cliente, 
        x = ~semana_visita, 
        y = ~cantidad_entregas, 
        color = ~cliente, 
        type = 'scatter', 
        mode = 'lines+markers') %>%
  layout(title = "Cantidad de Entregas Semanales por Cliente",
         xaxis = list(title = "Semana de Visita Planificada"),
         yaxis = list(title = "Cantidad de Entregas"),
         legend = list(title = list(text = "Cliente")))
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

2.8 Análisis Geoespacial

# Crear una lista de fechas únicas extraídas de la columna fin_visita
fechas_unicas <- unique(as.Date(raw_data$fin_visita))

# Graficar las entregas por cliente con un slider para cambiar por fecha
plot_ly(
  raw_data,
  lat = ~latitud,
  lon = ~longitud,
  type = 'scattermapbox',
  mode = 'markers',
  color = ~cliente,  # Asigna un color distinto por cliente
  frame = ~as.Date(fin_visita),  # Agregar la fecha como frame para la animación
  marker = list(size = 7, opacity = 0.7),  # Ajusta el tamaño y la transparencia de los marcadores
  text = ~paste("Cliente:", cliente, "<br>Dirección:", direccion)  # Información al pasar el mouse
) %>%
  layout(
    mapbox = list(
      accesstoken = mapbox_token,
      center = list(lat = -34.6037, lon = -58.3816),  # Coordenadas de Buenos Aires
      zoom = 10,  # Nivel de zoom
      style = "open-street-map"  # Estilo del mapa
    ),
    title = "Mapa de Entregas en Buenos Aires por Cliente",
    margin = list(r = 0, t = 0, b = 0, l = 0)
  ) %>%
  animation_opts(
    frame = 500,  # Duración de cada frame en milisegundos
    transition = 0,  # Sin transiciones entre frames
    redraw = TRUE
  ) %>%
  animation_slider(
    currentvalue = list(prefix = "Fecha: ")
  )
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
# Mapa de calor de entregas
heatmap_data <- raw_data %>%
  group_by(latitud, longitud) %>%
  summarise(total_entregas = n())
`summarise()` has grouped output by 'latitud'. You can override using the
`.groups` argument.
# Graficar un mapa de calor para visualizar las zonas con mayor densidad de entregas
heatmap_plot <- plot_ly(
  heatmap_data,
  lat = ~latitud,
  lon = ~longitud,
  z = ~total_entregas,
  type = 'densitymapbox',
  colorscale = 'Viridis',
  radius = 10
) %>%
  layout(
    mapbox = list(
      accesstoken = mapbox_token,
      center = list(lat = -34.6037, lon = -58.3816),
      zoom = 10,
      style = "open-street-map"
    ),
    title = "Mapa de Calor de Entregas en Buenos Aires",
    margin = list(r = 0, t = 30, b = 0, l = 0)
  )

# Mostrar el mapa de calor
heatmap_plot
# Mejorar el mapa original con clustering de puntos
clustered_plot <- plot_ly(
  raw_data,
  lat = ~latitud,
  lon = ~longitud,
  type = 'scattermapbox',
  mode = 'markers',
  color = ~as.factor(cliente),
  marker = list(size = 7, opacity = 0.6),
  text = ~paste("Cliente:", cliente, "<br>Dirección:", direccion)
) %>%
  layout(
    mapbox = list(
      accesstoken = mapbox_token,
      center = list(lat = -34.6037, lon = -58.3816),
      zoom = 10,
      style = "open-street-map"
    ),
    title = "Mapa de Entregas en Buenos Aires por Cliente (con Clustering)",
    margin = list(r = 0, t = 30, b = 0, l = 0)
  )

# Mostrar el mapa mejorado con clustering
clustered_plot
Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels

Warning in RColorBrewer::brewer.pal(N, "Set2"): minimal value for n is 3, returning requested palette with 3 different levels
# 1. Gráfico de barras: Distribución de entregas por día de la semana
dia_entregas <- raw_data %>%
  group_by(dia_str) %>%
  summarise(total_entregas = n())

dia_entregas_plot <- ggplot(dia_entregas, aes(x = reorder(dia_str, total_entregas), y = total_entregas, fill = dia_str)) +
  geom_bar(stat = "identity") +
  labs(title = "Distribución de Entregas por Día de la Semana", x = "Día de la Semana", y = "Número de Entregas") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Mostrar el gráfico de barras
dia_entregas_plot

# 2. Gráfico de líneas: Entregas a lo largo del tiempo por mes
entregas_por_mes <- raw_data %>%
  group_by(mes) %>%
  summarise(total_entregas = n())

entregas_mes_plot <- ggplot(entregas_por_mes, aes(x = mes, y = total_entregas, group = 1)) +
  geom_line(color = "blue", size = 1) +
  geom_point(size = 2) +
  labs(title = "Entregas a lo Largo del Tiempo por Mes", x = "Mes", y = "Número de Entregas") +
  theme_minimal()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
# Mostrar el gráfico de líneas
entregas_mes_plot
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_line()`).
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_point()`).

# 3. Gráfico de cajas: Duración de visitas por cliente
duracion_visitas_plot <- ggplot(raw_data, aes(x = as.factor(cliente), y = duracion_visita_min, fill = as.factor(cliente))) +
  geom_boxplot() +
  labs(title = "Duración de Visitas por Cliente", x = "Cliente", y = "Duración de la Visita (minutos)") +
  theme_minimal()

# Mostrar el gráfico de cajas
duracion_visitas_plot
Warning: Removed 52 rows containing non-finite outside the scale range
(`stat_boxplot()`).

# 4. Gráfico de barras: Entregas por hora del día
entregas_por_hora <- raw_data %>%
  group_by(hora) %>%
  summarise(total_entregas = n())

entregas_hora_plot <- ggplot(entregas_por_hora, aes(x = hora, y = total_entregas, fill = as.factor(hora))) +
  geom_bar(stat = "identity") +
  labs(title = "Entregas por Hora del Día", x = "Hora del Día", y = "Número de Entregas") +
  theme_minimal()

# Mostrar el gráfico de barras de entregas por hora
dia_entregas_plot

# 5. Análisis de tiempos de inactividad
# Calcular el tiempo de inactividad entre entregas por cliente
raw_data <- raw_data %>%
  arrange(cliente, inicio_visita) %>%
  group_by(cliente) %>%
  mutate(tiempo_inactividad = as.numeric(difftime(inicio_visita, lag(fin_visita), units = "mins")))

# Limitar los valores extremos para mejorar la visualización
tiempo_inactividad_limited <- raw_data %>%
  filter(tiempo_inactividad < 1500 & tiempo_inactividad >= 0)

# Gráfico violin: Tiempos de inactividad por cliente
tiempo_inactividad_violin_plot <- ggplot(tiempo_inactividad_limited, aes(x = as.factor(cliente), y = tiempo_inactividad, fill = as.factor(cliente))) +
  geom_violin(trim = FALSE) +
  geom_jitter(width = 0.2, alpha = 0.4) +
  labs(title = "Tiempos de Inactividad por Cliente (Mejorado)", x = "Cliente", y = "Tiempo de Inactividad (minutos)") +
  theme_minimal()

# Mostrar el gráfico de violín de tiempos de inactividad
tiempo_inactividad_violin_plot

2.8.1 Analisis especifico por cliente

# 6. Análisis para el Cliente 20
cliente_20_data <- raw_data %>% filter(cliente == 20)

# Gráfico de barras: Entregas por día de la semana para Cliente 20
cliente_20_dia_entregas <- cliente_20_data %>%
  group_by(dia_str) %>%
  summarise(total_entregas = n())

cliente_20_dia_entregas_plot <- ggplot(cliente_20_dia_entregas, aes(x = reorder(dia_str, total_entregas), y = total_entregas, fill = dia_str)) +
  geom_bar(stat = "identity") +
  labs(title = "Distribución de Entregas por Día de la Semana para Cliente 20", x = "Día de la Semana", y = "Número de Entregas") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Mostrar el gráfico de barras para Cliente 20
cliente_20_dia_entregas_plot

# 7. Análisis para el Cliente 70
cliente_70_data <- raw_data %>% filter(cliente == 70)

# Gráfico de barras: Entregas por día de la semana para Cliente 70
cliente_70_dia_entregas <- cliente_70_data %>%
  group_by(dia_str) %>%
  summarise(total_entregas = n())

cliente_70_dia_entregas_plot <- ggplot(cliente_70_dia_entregas, aes(x = reorder(dia_str, total_entregas), y = total_entregas, fill = dia_str)) +
  geom_bar(stat = "identity") +
  labs(title = "Distribución de Entregas por Día de la Semana para Cliente 70", x = "Día de la Semana", y = "Número de Entregas") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Mostrar el gráfico de barras para Cliente 70
cliente_70_dia_entregas_plot 

# Gráfico de líneas: Entregas por hora del día para Cliente 20 y Cliente 70
cliente_20_hora_entregas <- cliente_20_data %>%
  group_by(hora) %>%
  summarise(total_entregas = n())

cliente_70_hora_entregas <- cliente_70_data %>%
  group_by(hora) %>%
  summarise(total_entregas = n())

cliente_20_hora_entregas_plot <- ggplot(cliente_20_hora_entregas, aes(x = hora, y = total_entregas, fill = as.factor(hora))) +
  geom_bar(stat = "identity") +
  labs(title = "Entregas por Hora del Día para Cliente 20", x = "Hora del Día", y = "Número de Entregas") +
  theme_minimal()

cliente_70_hora_entregas_plot <- ggplot(cliente_70_hora_entregas, aes(x = hora, y = total_entregas, fill = as.factor(hora))) +
  geom_bar(stat = "identity") +
  labs(title = "Entregas por Hora del Día para Cliente 70", x = "Hora del Día", y = "Número de Entregas") +
  theme_minimal()

# Mostrar gráficos de barras por hora del día para Cliente 20 y Cliente 70
cliente_20_hora_entregas_plot
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_bar()`).

cliente_70_hora_entregas_plot

# Gráfico de líneas combinado: Entregas por hora del día para Cliente 20 y Cliente 70
cliente_20_hora_entregas <- cliente_20_data %>%
  group_by(hora) %>%
  summarise(total_entregas = n()) %>%
  mutate(cliente = "Cliente 20")

cliente_70_hora_entregas <- cliente_70_data %>%
  group_by(hora) %>%
  summarise(total_entregas = n()) %>%
  mutate(cliente = "Cliente 70")

# Unir los datos de ambos clientes para el gráfico combinado
total_entregas_por_hora <- bind_rows(cliente_20_hora_entregas, cliente_70_hora_entregas)

# Crear el gráfico combinado
entregas_hora_combined_plot <- ggplot(total_entregas_por_hora, aes(x = hora, y = total_entregas, fill = cliente)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(title = "Entregas por Hora del Día para Clientes 20 y 70", x = "Hora del Día", y = "Número de Entregas", fill = "Cliente") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Mostrar gráfico combinado
entregas_hora_combined_plot
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_bar()`).